package com.atguigu.tingshu.common.aspect;


import cn.hutool.core.util.RandomUtil;
import com.atguigu.tingshu.common.cache.DataCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Aspect
@Component
public class DataCacheAspect {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(dataCache)")
    public Object around(ProceedingJoinPoint joinPoint, DataCache dataCache) throws Throwable {
        try {
            //1.优先从缓存中获取业务数据
            //1.1 将方法参数采用"_"进行拼接作为Key的一部分
            String params = "none";
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length > 0) {
                params = Arrays.stream(args)
                        .map(Object::toString)
                        .collect(Collectors.joining("_"));
            }
            //1.2 构建缓存业务数据Key
            String dataKey = dataCache.prefix() + params;
            //1.3 查询Redis中缓存数据
            Object result = redisTemplate.opsForValue().get(dataKey);
            if (result != null) {
                return result;
            }

            //2.获取分布式锁：基于Redisson框架提供分布式锁
            //2.1 构建锁Key 形式=业务Key+锁后缀
            String lockKey = dataKey + RedisConstant.CACHE_LOCK_SUFFIX;
            //2.2 基于RedissonClient对象创建锁对象
            RLock lock = redissonClient.getLock(lockKey);

            //2.3 尝试获取分布式锁
            boolean flag = lock.tryLock(1, TimeUnit.SECONDS);

            //3.获取锁成功，执行查库业务方法（目标方法）
            if (flag) {
                try {
                    //3.1 执行目标查询数据库方法
                    result = joinPoint.proceed();
                    //3.2 将查询结果放入Redis中
                    long ttl = dataCache.ttl() + RandomUtil.randomInt(1200);
                    redisTemplate.opsForValue().set(dataKey, result, ttl, dataCache.timeUnit());
                    return result;
                } finally {
                    //3.3 释放锁
                    lock.unlock();
                }
            } else {
                //4.获取锁失败，自旋，可能就会命中缓存
                Thread.sleep(500);
                return this.around(joinPoint, dataCache);
            }
        } catch (Throwable e) {
            //5.兜底处理：如果Redis服务不可用，则执行目标方法
            log.error("Redis服务异常：{}", e.getMessage());
            return joinPoint.proceed();
        }
    }
}
